/* Copyright (C) 2021 Rain */

/* This file is part of CFS. */

/* 
  CFS is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  CFS is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with CFS. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>

#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/time.h>

#define __CFS
#include "cfs.h"


void fill_super(int);
void init_bim_buffer(int);

__bn_t bmap(int, __inode_t, __bn_t);
__bn_t create_block(int, __inode_t, __bn_t);

void lsdir(int, struct inode *);
struct inode *search_file(int, struct inode *, char *);

struct inode *create_file(int, struct inode *, char *, __uint8_t);
struct inode *mkdir(int, struct inode *, char *, __uint8_t);

__uint32_t gettime(void);

__uint32_t write_file(int, struct inode *, char *, __uint32_t);
__uint32_t read_file(int, struct inode *, char *, __uint32_t);

void dsync(int, struct inode *, char *);


#define sync_sb() 					\
	lseek(fd, 512, SEEK_SET);			\
	write(fd, &sb, sizeof(struct superblock));	\
	lseek(fd, 0, SEEK_SET);


int main(int argc, const char **argv) {
	char *fname = (char *) argv[1];
	int fd, bootfd, loaderfd, sysfd;

	if (argc < 5) {
		printf("fatal: excepted four arguments, but %d given. \n", argc - 1);
		printf("usage: %s device boot loader ksetup\n", argv[0]);

		return 1;
	}
	
	fd = open(fname, O_RDWR);

	if (fd < 0) {
		printf("fatal: failed when try to open %s: %s\n", fname, strerror(errno));
		return 1;
	}

	fill_super(fd);
	init_bim_buffer(fd);

	/* write boot to mbr, loader to that space that after superblock */
	bootfd = open(argv[2], O_RDONLY);
	loaderfd = open(argv[3], O_RDONLY);

	/* sys (vmxnix) to a file that called vmxnix */
	sysfd = open(argv[4], O_RDONLY);

	__uint32_t fsize = lseek(sysfd, 0, SEEK_END);
	lseek(sysfd, 0, SEEK_SET);

	char *buf = malloc(fsize);

	if (!buf) {
		printf("fatal: failed when allocate space: %u bytes need. ", fsize);

		close(fd);
		close(bootfd);
		close(sysfd);
	}

	int bytesread;

	bytesread = read(bootfd, buf, 512);
	lseek(fd, 0, SEEK_SET);
	write(fd, buf, 512);

	close(bootfd);

	printf("boot: %d bytes. \n", bytesread);

	/* max bytes for loader */
	bytesread = read(loaderfd, buf, BLOCK_SIZE - 512 - sizeof(struct superblock));
	lseek(fd, 512 + sizeof(struct superblock), SEEK_SET);
	write(fd, buf, bytesread);

	close(loaderfd);

	printf("loader: %d bytes\n", bytesread);

	bytesread = read(sysfd, buf, fsize);

	struct inode *ino = create_file(fd, &sb.root_ino, "ksetup", 
			A_ROOTRD | A_USERRD | A_REGFILE | A_EXEC);

	if (!ino) 
		printf("file vmxnix exists, not continuing. \n");

	else
		write_file(fd, ino, buf, fsize);

	close(fd);

	printf("vmxnix: %d bytes. \n", bytesread);

	return 0;
}


void fill_super(int fd) {
	/* read super block from fd */

	lseek(fd, 512, SEEK_SET);
	read(fd, &sb, sizeof(struct superblock));

	if (sb.magic != MAGIC) {
		printf("fatal: bad magic number. \n");

		close(fd);
		exit(1);
	}

	return;
}


/* 
 * initial the bim buffer. (struct bim_buffer). 
 * : read the first block of bim to buffer, and 
 *   clear 'dirty'. 
 * */
void init_bim_buffer(int fd) {
	bimbuf.this_block = sb.bim_bn;
	bimbuf.dirty = 0;
	bimbuf.bn_start = sb.first_data_block;

	bimbuf.buffer = (struct bim_entry *) malloc(BLOCK_SIZE);

	if (!bimbuf.buffer) {
		printf("fatal: unable to allocate space for bim buffer. (%d bytes requested) \n", BLOCK_SIZE);

		close(fd);
		exit(1);
	}

	lseek(fd, sb.bim_bn << BLOCK_SHIFT, SEEK_SET);
	read(fd, bimbuf.buffer, BLOCK_SIZE);

	return;
}


void read_inode(int fd, __inode_t ino, struct inode *inode) {
	lseek(fd, (sb.inodes_bn << BLOCK_SHIFT) + ino * sizeof(struct inode), SEEK_SET);
	read(fd, inode, sizeof(struct inode));

	return;
}

void write_inode(int fd, __inode_t ino, struct inode *inode) {
	lseek(fd, (sb.inodes_bn << BLOCK_SHIFT) + ino * sizeof(struct inode), SEEK_SET);
	write(fd, inode, sizeof(struct inode));

	return;
}


/* 
 * a function that used to rw inodes. (get the `bn`th block of inode `ino`)
 *
 * STEPS: 
 * 	1. check in bim buffer. 
 * 		1a. (found):     return real block. 
 * 		1b, (not-found): read buffer again (next bim block) and check again. 
 * */

__bn_t bmap(int fd, __inode_t ino, __bn_t bn) {
	__bn_t i;
	struct bim_entry *buf;
       
repeat:
	buf = bimbuf.buffer;

	/* search in buf */
	for (i = 0; i < BLOCK_SIZE / sizeof(struct bim_entry); i++) {
		if (buf[i].inode == ino  && buf[i].th_block == bn) 
			return i + bimbuf.bn_start;
	}

	/* not found, so load buffer again and repeat */
	bimbuf.this_block++;
	bimbuf.dirty = 0;

	/* += how many blocks it can describe */
	bimbuf.bn_start += BLOCK_SIZE / sizeof(struct bim_entry);

	lseek(fd, bimbuf.this_block << BLOCK_SHIFT, SEEK_SET);
	read(fd, bimbuf.buffer, BLOCK_SIZE);

	goto repeat;

	return 0;
}


void sync_bimbuf(int fd) {
	lseek(fd, bimbuf.this_block << BLOCK_SHIFT, SEEK_SET);
	write(fd, bimbuf.buffer, BLOCK_SIZE);
	bimbuf.dirty = 0;

	return;
}


/* 
 * find a new free block, and register it in bim buffer and device. 
 * */

__bn_t create_block(int fd, __inode_t ino, __bn_t bn) {
	__bn_t i;
	struct bim_entry *buf;

repeat:
	buf = bimbuf.buffer;

	for (i = 0; i < BLOCK_SIZE / sizeof(struct bim_entry); i++) {
		if (!buf[i].inode) {
			/* register it in buffer first */
			buf[i].inode = ino;
			buf[i].th_block = bn;

			/* and write it back to device (sync) */
			sync_bimbuf(fd);

			return i + bimbuf.bn_start;
		}
	}

	bimbuf.this_block++;
	bimbuf.dirty = 0;
	bimbuf.bn_start += BLOCK_SIZE / sizeof(struct bim_entry);

	read(fd, bimbuf.buffer, BLOCK_SIZE);

	goto repeat;

	return 0;
}


/* list the files in root directory, until it's empty */
void lsdir(int fd, struct inode *dir) {
	int i;
	struct dentry *entries = (struct dentry *) malloc(BLOCK_SIZE);

	for (i = 0; i < dir->blocks_nr; i++) {
		lseek(fd, bmap(fd, dir->inode, i) << BLOCK_SHIFT, SEEK_SET);
		read(fd, entries, BLOCK_SIZE);

		int j;

		for (j = 0; j < BLOCK_SIZE / sizeof(struct dentry) && entries[j].ino; j++) {
			if (!entries[j].ino) continue;
			printf("%d: %s\n", j, entries[j].name);
		}
	}

	return;
}


/* 
 * search file 'name' in directory 'dir'. 
 *
 * 'name' is only a file name, unlike '/foo.bar' or '/foo/bar.foo'
 * */

struct inode *search_file(int fd, struct inode *dir, char *name) {
	/* read root directory until find file 'name' */

	int i;
	struct dentry *entries = (struct dentry *) malloc(BLOCK_SIZE);
	struct inode *ino = malloc(sizeof(struct inode));

	for (i = 0; i < dir->blocks_nr; i++) {
		lseek(fd, bmap(fd, dir->inode, i) << BLOCK_SHIFT, SEEK_SET);
		read(fd, entries, BLOCK_SIZE);

		int j;

		for (j = 0; j < BLOCK_SIZE / sizeof(struct dentry); j++) {
			if (strncmp(entries[j].name, name, FNAME_LEN) == 0) {
				read_inode(fd, entries[j].ino, ino);
				return ino;
			}
		}
	}

	/* root directory is dirty so it runs here. */
	return NULL;
}


/* 
 * sync the directory 'inode' (write all data blocks back)
 * (calls from create file or other functions that changed inode of the 
 * child of this directory)
 * */
void dsync(int fd, struct inode *inode, char *entries) {
	int i;

	for (i = 0; i < inode->blocks_nr; i++) {
		lseek(fd, bmap(fd, inode->inode, i) << BLOCK_SHIFT, SEEK_SET);
		write(fd, entries + (i << BLOCK_SHIFT), BLOCK_SIZE);
	}

	return;
}



/* 
 * create a file 'name' in directory 'dir' with access mode 'acc'. 
 * */
struct inode *create_file(int fd, struct inode *dir, char *name, __uint8_t acc) {
	/* find a free entry in root directory */

	int i;
	struct dentry entries[BLOCK_SIZE / sizeof(struct dentry)];
	struct inode *ino = malloc(sizeof(struct inode));

	memset(ino, 0x00, sizeof(struct inode));

	for (i = 0; i < sb.root_ino.blocks_nr; i++) {
		lseek(fd, bmap(fd, dir->inode, i) << BLOCK_SHIFT, SEEK_SET);
		read(fd, entries, BLOCK_SIZE);

		int j;

		/* first: check this file is non exists */
		for (j = 0; j < BLOCK_SIZE / sizeof(struct dentry); j++) {
			if (strncmp(entries[j].name, name, FNAME_LEN) == 0) {
				printf("create_file: failed: file '%s' exists (inode %d)\n", 
					name, entries[j].ino);

				return 0;
			}
		}

		for (j = 0; j < BLOCK_SIZE / sizeof(struct dentry); j++) {
			if (!entries[j].ino) {
				ino->inode = entries[j].ino = sb.next_inode++;

				ino->pinode = ROOT_INO;

				ino->access = acc;
				ino->blocks_nr = ino->upsize = 0;

				ino->ctime = ino->wtime = gettime();
				ino->nlinks = 1;

				ino->uid = ino->gid = 0;
				ino->eaccess = 0;

				/* copy file name */
				strncpy(entries[j].name, name, FNAME_LEN);
				sync_sb();
				
				dsync(fd, dir, (char *) entries);

				return ino;
			}
		}
	}

	printf("fatal: create_file: no enough space in this directory\n");

	return 0;
}


__uint32_t write_file(int fd, struct inode *inode, char *__buf, __uint32_t nbytes) {
	/* bad bad, we shouldn't do this */

	char *buffer = malloc(ALIGNUP_BLOCK(nbytes));
	int i;

	__uint32_t nblocks = ALIGNUP_BLOCK(nbytes) >> BLOCK_SHIFT;

	memcpy(buffer, __buf, nbytes);

	for (i = 0; i < nblocks; i++) {
		lseek(fd, create_block(fd, inode->inode, inode->blocks_nr + i) << BLOCK_SHIFT, SEEK_SET);
		write(fd, buffer + (i << BLOCK_SHIFT), BLOCK_SIZE);
	}

	inode->blocks_nr = (nbytes & BLOCK_MASK) >> BLOCK_SHIFT;
	inode->upsize = nbytes - (inode->blocks_nr << BLOCK_SHIFT);

	inode->wtime = gettime();

	free(buffer);

	/* sync, then we can return */
	write_inode(fd, inode->inode, inode);

	return nbytes;
}


__uint32_t read_file(int fd, struct inode *inode, char *__buf, __uint32_t nbytes) {
	/* first read blocks that aligned */
	__uint32_t nblocks = (nbytes & BLOCK_MASK) >> BLOCK_SHIFT;
	int i;

	for (i = 0; i < nblocks; i++) {
		lseek(fd, bmap(fd, inode->inode, i) << BLOCK_SHIFT, SEEK_SET);
		read(fd, __buf + (i << BLOCK_SHIFT), BLOCK_SIZE);
	}

	char buf[BLOCK_SIZE];

	lseek(fd, bmap(fd, inode->inode, i) << BLOCK_SHIFT, SEEK_SET);
	read(fd, buf, BLOCK_SIZE);
	memcpy(__buf + (i << BLOCK_SHIFT), buf, nbytes - (nbytes & BLOCK_MASK));

	return nbytes;
}


__uint32_t gettime(void) {
	struct timeval t = {0};

	gettimeofday(&t, NULL);
	return t.tv_sec;
}


struct inode *mkdir(int fd, struct inode *dir, char *name, __uint8_t acc) {
	struct inode *new_dir = malloc(sizeof(struct inode));

	/* first create a file in directory 'dir' */
	if (!(new_dir = create_file(fd, dir, name, acc & A_DIR & A_EXEC))) {
		return NULL;
	}

	new_dir->blocks_nr = 1;
	create_block(fd, new_dir->inode, 0);

	/* then create '.' and '..' in new directory */
	create_file(fd, new_dir, ".", A_ROOT_RW | A_USER_RW | A_DIR | A_LINK);
	create_file(fd, new_dir, "..", A_ROOT_RW | A_USER_RW | A_DIR | A_LINK);

	write_inode(fd, new_dir->inode, new_dir);

	return new_dir;
}
